iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
0
Mobile Development

傻瓜也要能看懂的Flutter學習心得系列 第 8

[Day 8]無法逃避的宿命:介紹資料類型,函數如何接收資料,函數如何回傳資料

  • 分享至 

  • xImage
  •  

就像宿命無法逃避一樣,介紹完了物件和設計物件的基本方法,還講了如何使用物件設置資料,現在該來介紹如何使用基本類型(又稱為基本型別)的資料了。

但開始介紹前,可能有人會問:為什麼不先直接介紹基本型別?不是「基本」嗎?

(按照慣例,接下來的介紹是偏向概念上,都比較簡略,並不是絕對正確精準。)

通常在高階程式語言中,所有的資料是用八位元為基本單位在做記憶體管理儲存,如果有筆資料是十六位元,那它其實是用「資料:存放在記憶體N到記憶體N+1之間」的方式設置,如果有筆資料是三十二位元,那它其實是用「資料:存放在記憶體N到記憶體N+3之間」的方式設置。

如果程式要取用資料,例如取用三十二位元的資料,那系統會自動將N、N+1、N+2、N+3的記憶體欄位的資料合併起來,四個八位元會成為一個三十二位元。

以上的舉例其實在實務上有個嚴重的問題:現實中電腦已經很少用八位元為基本單位在做記憶體管理。

沿用這個「八位元模式」舉例是因為:所謂的基本類型資料在使用跟管理上,機制跟物件資料的使用跟管理是有相似之處的。

所以在很多高階語言中,即使是基本型別也都是物件。

繼續無法逃避的宿命。

一般高階程式語言(這裡是指從C衍伸出來的)提供布林值boolean、八位元、十六位元、三十二位元、六十四位元、字串這幾種資料型態。

但在Flutter,資料型態主要就布林值boolean、長整數Integer(三十二位元)、浮點數Double(六十四位)、和St2ing'串這幾種'

設置資料時,在程式碼內要使用bool、int、double、String作為資料的類型名稱。

bool booleanData = true;

int integerData = 0;

double doubleData = 0.0003;

String stringData1 = "測試資料";

String stringData2 = '測試資料';

什麼是長整數、什麼是浮點數?

簡單來說,前者是「整數」,後者是「有小數點」的數值。

要注意,如果在長整數(無小數點整數)後面賦予浮點數(有小數點)資料(或反過來),可能會導致程度不一的錯誤,例如無法編譯或無法執行,或單純的結果不如你預期,所以不可以混用。

但運算時卻又容許某些程度的例外,例如將長整數和浮點數相加後,會自動產生浮點數結果。(所以要用一個浮點數資料存放結果。)

什麼是布林值?

布林值是用來簡單紀錄「true/false」的一種「(理論上)只佔了一個位元」的資料型態。在某些高階語言(或組合語言)中,它可以直接被視為「1/0」,(甚至可能沒有這種資料型態,要管理紀錄「true/false」,必須要使用「位元運算」。)

以上幾種資料類型都可以混搭在物件中,所以如果用這種方式重新編寫昨天介紹的「圖片物件」,裏頭要有「檔案路徑、顯示座標、畫質、旋轉角度」等資料,會變成這樣...

class Image{

    String filePath;
    
    int positionX;
    int positionY;
    
    int level;
    
    double angle;
    
}

光是宣告資料未免太無趣,順便幫這個物件加上函數吧!

這時候可以開始思考為什麼要使用函數、要增加什麼樣的函數?

先說為什麼?

拿「角度」來說,常識來說,我們習慣用360度來表示角度,但在很多程式語言中卻是使用「pi值(0~3.14)」來定義角度。甚至還有完全自己定義的規格,例如「0~100」。

假設angle是用「pi值(0~3.14)」,但為了讓使用這個物件的人(未必是設計這個物件的人)可以快速使用,就要讓使用者可以用360度的方式來思考關於角度變化的問題。

所以乾脆讓使用者可以直接用360度的方式來輸入數值調整角度吧!

怎麼做?現在我們有了理由、也有了要做的目標,那就來做成函數好了。

void changeAngle(){
int changedAngle = 90;
double changedPiAngle = changedAngle/180;
angle = angle + changedPiAngle;
}

有沒有覺得怪怪的?這函數的意思是說「每次執行都固定轉動90度」,但不可能每次都轉動90度啊!(也不可能一次只轉動1度,需要轉動幾度就連續執行這個函數幾次。)

所以函數必須要可以接受使用函數的人(和設計的程式)去自由決定要轉動的角度數值,這個可以自由決定的數值,被稱為「參數 Parameter」(或說「傳入參數」)。

void changeAngle(int changedAngle){
double changedPiAngle = changedAngle/180;
angle = angle + changedPiAngle;
}

可以看到「changedAngle」這個資料被從函數內移動到函數名稱後方的「()」。

如果要使用這個函數,就必須......

Image img = new Image(......);

img.changeAngle(10);
img.changeAngle(20);
img.changeAngle(-1);

為什麼要用「img.changeAngle」這種方式來使用函數?「runApp」這個函數就沒有這麼複雜。

用一個「不可行」的範例來說明吧...

Image img1 = new Image(......);
Image img2 = new Image(......);

changeAngle(10);
changeAngle(20);
changeAngle(-1);

請問這三次的「changeAngle」執行後,要去修改哪個Image內的角度呢?

首先,這是為何要用「img.changeAngle」這種方式的原因,程式必須要精準的知道函數是修改哪一組資料。

也就是說「如果不需要用「img.changeAngle」這種方式也能知道函數會修改哪一組資料時,自然就不需要用「img.changeAngle」。

但何時會允許這樣做呢?

這有兩種情況...要說明,首先我們在Image物件內新增一組參數,就直接稱為「demoAngle」吧!這個函數的目的就是單純的變換30度角。

void demoAngle(){
    changeAngle(30);
}

這時候就不用「img.changeAngle」這種方式,可以直接用「changeAngle」這種方式,就跟「runApp」一樣。

為什麼?因為在這個函數內,它知道自己屬於哪個物件資料,自然也知道要去修改哪個物件資料。會需要用「img.changeAngle」這種方式是為了讓函數外、物件外的程式知道這個「changeAngle」要作用在哪個物件資料上。

所以如果要在物件外使用「demoAngle」,自然也要像使用「img.changeAngle」這種方式一樣,用「img.demoAngle」來使用函數。

Image img1 = new Image(......);
Image img2 = new Image(......);

img1.demoAngle();
img2.changeAngle(10);

所以為何「runApp」不需要像使用「img.changeAngle」這種方式一樣?因為當時它就是在物件內使用,所以它自然知道要去使用哪個物件。

接著解釋一下什麼是「void」.......沒辦法解釋。

void是什麼意思?...無法解釋。

雖然無法解釋,但不表示我們無法用直覺的方式理解它。

假設...Image物件中的資料大多可以經由設定而來,例如設定一組Image資料時,可以同時使用建構子決定它的檔案路徑。

class Image {
    String filePath;
    Image(String path){
        filePath = path;
    }
}

(注意!以上作法是其他高階程式語言對建構式常用的規則。在Flutter中有超級簡單的作法,但先不介紹,因為那很複雜。)

要設置一個Image資料,就要有圖片路徑。

String filePath = "...";
Image demoImg = Image(filePath);

而既然有圖片路徑,那讀取出圖片後就會有圖片檔案大小,但這個檔案大小卻無法經由設定來知道,──因為路徑名稱裡面通常不會寫檔案大小。

所以(先不去探問「我們還沒給Image物件讀取檔案的能力」)我們要讓物件外的程式可以知道圖片大小,就要設置一組函數和一個紀錄檔案大小的資料。

class Image {
    String filePath;
    int fileSize;
    
    Image(String path){
        filePath = path;
    }
    
    int imgSize(){
        return fileSize;
    }
}

注意看,在「chanegAngle」中本來是void的地方,在「imgSize」變成了基本資料型別「int」,裡面還寫了個「return fileSize」這會有什麼影響?

先不解釋,接下來直接看使用的方法...

String filePath = "...";
Image demoImg = Image(filePath);
int size = demoImg.imgSize();

注意看,這裡有個「int」型別的資料「size」,但後面接受的數值卻是個「正在執行函數的物件資料」。

不使用void的函數會有「返回值」,這時候可以將函數當成一個數值來使用,因為這個函數最終的執行結果就是「產生一個數值」。

有數值,就可以將它設在給資料去存儲管理。

所以「int size = demoImg.imgSize();」這樣的用法才能成立。

但要注意幾件事!

指定了資料型別後,「return」後面使用的資料型別要「吻合」,不可以寫了int卻返回String。

另外,很明顯的,物件也可以作為返回值,例如Image這個物件的資料就可以當作返回值。

有返回值這特性這並不會改變函數依舊是函數的事實。使用「imgSize」或任何有返回值的函數,前面並不一定需要一個資料欄位來接收結果,例如可以直接使用「demoImg.imgSize();」,前面不需要有資料欄位。

除了「建構式」以後,原則上所有函數都要有void或回傳類型...原則上。(例外長什麼樣?怎麼用?這篇文章可以作為參考。但我個人是不會使用這種方法寫程式碼的。這種技巧就是典型的偏離了程式設計的正道,把寫程式搞成「精通各種小招式」。)


上一篇
[Day 7]函數和物件之間的關係,開始設置參數
下一篇
[Day 9]Array/List和迴圈
系列文
傻瓜也要能看懂的Flutter學習心得10
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言